/**
 * @author: yangcheng
 * @date: 2020年6月20日上午11:37:39
 * @description:
 * @modify:
 */
package com.topscomm.sys.redis;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.connection.lettuce.LettuceConnection;
import org.springframework.data.redis.core.StringRedisTemplate;

import com.topscomm.pub.util.SpringUtil;

/**
 * @author: yangcheng
 * @date: 2020年6月20日上午11:37:39
 * @description: 分布式锁
 * @modify:
 */
public class RedisLock implements Lock {
	private static RedisLock instance = null;
	protected StringRedisTemplate redisStringTemplate;
	protected JedisConnection connection = null;
	// 存储到redis中的锁标志
	private static final String LOCKED = "LOCKED";
	// 请求锁的超时时间(ms)
	private static final long TIME_OUT = 30000;
	// 锁的有效时间(s)
	public static final int EXPIRE = 60;
	// 锁标志对应的key;
	private String key;
	// state flag
	private volatile boolean isLocked = false;

	private RedisLock() {
		redisStringTemplate = (StringRedisTemplate) SpringUtil.getBean("stringRedisTemplate");
		connection = (JedisConnection) redisStringTemplate.getConnectionFactory().getConnection();
	}

	public RedisLock(String key) {
		this.key = key;
		redisStringTemplate = (StringRedisTemplate) SpringUtil.getBean("stringRedisTemplate");
		connection = (JedisConnection) redisStringTemplate.getConnectionFactory().getConnection();
	}

	/**
	 * @author: yangcheng
	 * @date: 2020年6月28日下午3:19:58
	 * @params: @return
	 * @return_type: RedisLock
	 * @description: DCL单例
	 * @modify:
	 */
	public static RedisLock getInstance() {
		if (instance == null) {
			synchronized (RedisLock.class) {
				if (instance == null) {
					instance = new RedisLock();
				}
			}
		}
		return instance;
	}

	/**
	 * @author: yangcheng
	 * @date: 2020年6月22日上午10:18:45
	 * @params: @return
	 * @return_type: Boolean
	 * @description: redis上锁
	 * @modify:
	 */
	@SuppressWarnings("deprecation")
	public Boolean lock(String key) {
		// 将锁作为key存储到redis缓存中，存储成功则获得锁
		if (connection.setNX(key.getBytes(), LOCKED.getBytes())) {
			redisStringTemplate.expire(key, EXPIRE, TimeUnit.SECONDS);
			isLocked = true;
			return true;
			// 上锁成功结束请求
		} else {
			return false;
		}
	}

	/**
	 * @author: yangcheng
	 * @date: 2020年6月28日下午3:22:14
	 * @params: @param key
	 * @return_type: void
	 * @description: redis解锁
	 * @modify:
	 */
	public void unlock(String key) {
		// 释放锁
		// 不管请求锁是否成功，只要已经上锁，客户端都会进行释放锁的操作
		if (isLocked) {
			redisStringTemplate.delete(key);
		}
	}

	@Override
	public void lock() {
		// 系统当前时间，毫秒
		long nowTime = System.nanoTime();
		// 请求锁超时时间，毫秒
		long timeout = TIME_OUT * 1000000;
		Random r = null;
		try {
			r = SecureRandom.getInstanceStrong();
		} catch (NoSuchAlgorithmException e1) {
			e1.printStackTrace();
		}
		try {
			// 不断循环向Master节点请求锁，当请求时间(System.nanoTime() - nano)超过设定的超时时间则放弃请求锁
			// 这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间
			// 如果一个master节点不可用了，应该尽快尝试下一个master节点
			while ((System.nanoTime() - nowTime) < timeout) {
				// 将锁作为key存储到redis缓存中，存储成功则获得锁
				if (connection.setNX(key.getBytes(), LOCKED.getBytes())) {
					// 设置锁的有效期，也是锁的自动释放时间，也是一个客户端在其他客户端能抢占锁之前可以执行任务的时间
					// 可以防止因异常情况无法释放锁而造成死锁情况的发生
					redisStringTemplate.expire(key, EXPIRE, TimeUnit.SECONDS);
					isLocked = true;
					// 上锁成功结束请求
					break;
				}
				// 获取锁失败时，应该在随机延时后进行重试，避免不同客户端同时重试导致谁都无法拿到锁的情况出现
				// 睡眠3毫秒后继续请求锁
				Thread.sleep(3, r.nextInt(500));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void unlock() {
		// 释放锁
		// 不管请求锁是否成功，只要已经上锁，客户端都会进行释放锁的操作
		if (isLocked) {
			redisStringTemplate.delete(key);
		}
	}

	@Override
	public void lockInterruptibly() throws InterruptedException {
		// TODO Auto-generated method stub
	}

	@Override
	public boolean tryLock() {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public Condition newCondition() {
		// TODO Auto-generated method stub
		return null;
	}
}
